﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Diagnostics;
using System.Text;
using ProtoBuf;
using ProtoBuf.Meta;
using HiTimer;
using Engine;
using SharpDX;
using SharpDX.D3DCompiler;
using SharpDX.Direct3D11;
using SharpDX.Direct3D;
using SharpDX.DXGI;
using SharpDX.Windows;
using SharpDX.DirectInput;
using SharpDX.Multimedia;
using SharpDX.RawInput;
using SharpDX.Direct3D11;
using Device = SharpDX.Direct3D11.Device;
using Resource = SharpDX.Direct3D11.Resource;
using ModelViewer;

namespace Engine
{
    public sealed class Scene : System.IDisposable
    {
        public RenderTargetView HDRTarget;
        public Texture2D HDRTexture;
        public ShaderResourceView HDRSRV;

        SamplerState HDRSS;

        public DepthStencilView DepthStencilView;
        public RenderTargetView renderTarget;
        public Texture2D DepthStencil;

        public EELightShadow Light;
        public EEDCamera Camera;

        public EESkyBox SkyBox;
        Boolean RebderCube = true;
        public bool InitCubeMap = true;

        public List<GameObject> AllObjects = new List<GameObject> { };

        public Vector3 LightDirection = new Vector3(0.0f, -0.5f, 0.5f);
        
        public Texture2D DepthTexture;
        public ShaderResourceView DepthSRV;
        public RenderTargetView DepthTarget;

        public DepthStencilView DepthMapStencilView;
        public Texture2D DepthMapStencil;

        //List<GSSprite> TS;
        //InfoQueue IQ = new InfoQueue()

        TextureContainer TC1;
        TextureContainer TC2;

        public void Inizialize(Device device, Viewport viewport, SwapChainDescription description, DeviceContext context, SwapChain swapChain)
        {
            int S = Global.Settings.MultiSample;
            if (S == 0) S++;

            SampleDescription _SampleDescription = new SampleDescription(S, 0);
            //Буфер глубины
            Texture2DDescription DepthStencilDescription = new Texture2DDescription();
            DepthStencilDescription.Width = (int)viewport.Width;
            DepthStencilDescription.Height = (int)viewport.Height;
            DepthStencilDescription.MipLevels = 1;
            DepthStencilDescription.ArraySize = 1;
            DepthStencilDescription.Format = Format.D32_Float;
            DepthStencilDescription.SampleDescription = _SampleDescription;
            DepthStencilDescription.Usage = ResourceUsage.Default;
            DepthStencilDescription.BindFlags = BindFlags.DepthStencil;
            DepthStencilDescription.CpuAccessFlags = CpuAccessFlags.None;
            DepthStencilDescription.OptionFlags = ResourceOptionFlags.None;
            DepthStencil = new Texture2D(device, DepthStencilDescription);
            DepthStencilView = new DepthStencilView(device, DepthStencil);

            //HDR буффер
            Texture2DDescription HDRDescription = new Texture2DDescription();
            HDRDescription.MipLevels = 1;
            HDRDescription.ArraySize = 1;
            HDRDescription.Width = (int)viewport.Width;
            HDRDescription.Height = (int)viewport.Height;
            HDRDescription.Format = Format.R16G16B16A16_Float;
            HDRDescription.SampleDescription = _SampleDescription;
            HDRDescription.Usage = ResourceUsage.Default;
            HDRDescription.BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource;
            HDRDescription.CpuAccessFlags = CpuAccessFlags.None;
            HDRDescription.OptionFlags = ResourceOptionFlags.None;
            HDRTexture = new Texture2D(device, HDRDescription);
            HDRSRV = new ShaderResourceView(device, HDRTexture);

            RenderTargetViewDescription RTVD = new RenderTargetViewDescription();
            RTVD.Format = HDRDescription.Format;
            RTVD.Dimension = RenderTargetViewDimension.Texture2DMultisampled;
            RTVD.Texture2DMSArray.FirstArraySlice = 0;
            RTVD.Texture2DMSArray.ArraySize = 1;
            RTVD.Texture2D.MipSlice = 0;
            HDRTarget = new RenderTargetView(device, HDRTexture, RTVD);

            SamplerStateDescription a = new SamplerStateDescription();
            a.AddressU = TextureAddressMode.Wrap;
            a.AddressV = TextureAddressMode.Wrap;
            a.AddressW = TextureAddressMode.Wrap;
            a.Filter = Filter.MinMagMipPoint;
            HDRSS = new SamplerState(device, ref a);

            //Depth
            //////////////////////////////////////////////////////////////////////////
            Texture2DDescription DepthDescription = new Texture2DDescription();
            DepthDescription.Width = (int)viewport.Width;
            DepthDescription.Height = (int)viewport.Height;
            DepthDescription.MipLevels = 1;
            DepthDescription.ArraySize = 1;
            DepthDescription.Format = Format.R32_Float;
            DepthDescription.Usage = ResourceUsage.Default;
            DepthDescription.BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource;
            DepthDescription.SampleDescription = new SampleDescription(1, 0);
            DepthDescription.CpuAccessFlags = CpuAccessFlags.None;
            DepthDescription.OptionFlags = ResourceOptionFlags.None;
            DepthTexture = new Texture2D(device, DepthDescription);
            DepthSRV = new ShaderResourceView(device, DepthTexture);
            RenderTargetViewDescription DTVD = new RenderTargetViewDescription();
            DTVD.Format = DepthDescription.Format;
            DTVD.Dimension = RenderTargetViewDimension.Texture2D;
            DTVD.Texture2DMSArray.FirstArraySlice = 0;
            DTVD.Texture2DMSArray.ArraySize = 1;
            DTVD.Texture2D.MipSlice = 0;
            DepthTarget = new RenderTargetView(device, DepthTexture, DTVD);

            DepthStencilDescription.SampleDescription = new SampleDescription(1, 0);

            DepthMapStencil = new Texture2D(device, DepthStencilDescription);
            DepthMapStencilView = new DepthStencilView(device, DepthMapStencil);

            // create a view of our render target, which is the backbuffer of the swap chain we just created
            using (var resource = Resource.FromSwapChain<Texture2D>(swapChain, 0))
                renderTarget = new RenderTargetView(device, resource);

        }

        public void LoadContent(Device device)
        {
            Light = new EELightShadow(new DVector3(10, 40, 40), new Vector3(2.4f, 2.2f, 2.0f));
            Camera = new EEDCamera(Global.Settings.Width, Global.Settings.Height, 45, 1, 10000);

            //Camera = new EEDCamera(Global.Settings.Width, Global.Settings.Height, 90, 1, 1000);
            Light = new EELightShadow(new DVector3(10, 40, 40), new Vector3(2.4f, 2.2f, 2.0f));
            SkyBox = new EESkyBox();
            SkyBox.Initialize();
            //TestCube_UV2
            //"Content\\Models\\Argon_1_export.material"
            AllObjects.Add(new GameObject("Content\\Models\\Argon_1_export.material"));
            AllObjects.Add(new GameObject("Content\\Models\\Argon_1_export.material"));
            AllObjects.Add(new GameObject("Content\\Models\\TestCube_UV2.material"));

            AllObjects[1].TempSetPos(new DVector3(0, 50, -200));
            AllObjects[2].TempSetPos(new DVector3(-100, -1000, 1000));

            Random R = new Random(1);
            for (int y = 0; y < 10; y++)
            {
                GameObject GO = new GameObject("Content\\Models\\Argon_1_export.material");
                GO.TempSetPos(new DVector3(R.NextDouble(-300, 300), R.NextDouble(-500, 500), R.NextDouble(-500, 500)));
                AllObjects.Add(GO);
            }

            float yawn = (float)Math.PI/ 2f;
            float pitch = 0f;

            Vector3 t_vec = new Vector3(0, 0, 350);
            Quaternion quat = Quaternion.RotationYawPitchRoll((float)yawn, (float)pitch, 0);
            Camera.position = Conversion.ToDoubleVector(Vector3.Transform(t_vec, quat));// +target;
            Camera.quaternion = quat;

            /*
            TS = new List<GSSprite> { };

            R = new Random(2);
            for (int i = 0; i < 1; i++)
            {
                DVector3 t = new DVector3(R.NextDouble(-500, 500), R.NextDouble(-500, 500), R.NextDouble(-500, 500));
                TS.Add(new GSSprite(device, t));
            }*/
            TC1 = ContentManager.LoadTexture2D("Content/Textures/Particl");
            TC2 = ContentManager.LoadTexture2D("Content/Textures/Particl2");
        }

        double Iangle = 0;
        public void Update(float TimeMili)
        {
            
            double angle = Iangle;// 0.8f + (float)Math.Sin(Iangle) * 0.3f;

            DVector3 Pos = DVector3.Transform(new DVector3(0, 0, -1), DQuaternion.CreateFromYawPitchRoll(angle, -3.14159f / 5f, 0));
            Light.Position = Pos;

            //LightDirection = Conversion.ToVector3(Pos);
            //LightDirection = new Vector3(0, -1, 2f);
            /*
            double yawn = angle * 0.5d;
            double pitch = 0;// -3.14159f / 20f;
            
            yawn = 3.14f/2f;
            pitch = 0f;
            
            Vector3 t_vec = new Vector3(0, 0, 350);
            Quaternion quat = Quaternion.RotationYawPitchRoll((float)yawn, (float)pitch, 0);
            Camera.position = Conversion.ToDoubleVector(Vector3.Transform(t_vec, quat));// +target;
            Camera.quaternion = quat;
            */
            //Camera.position = new DVector3(100, 0, 0);
            //Camera.quaternion = Quaternion.RotationYawPitchRoll((float)Math.PI/2f, 0, 0); 
            FPSCamera.Update(TimeMili, Camera);

            //Camera.position = new DVector3(73.7091272993663, 52.6778095654317, 108.641878625747);
            //Camera.quaternion = new Quaternion(-0.1709949f, 0.3623056f, 0.06780182f, 0.9137277f);
            Camera.Update();

            Iangle += TimeMili / 4000f;

        }

        public void Draw(float TimeMili, Device device, DeviceContext context)
        {
            
            if (RebderCube)
            {
                RebderCube = false;
                SkyBox.RenderCubemap();
            }

            Random R = new Random(2);
            for (int i = 0; i < 0; i+=2)
            {
                DVector3 t = new DVector3(R.NextDouble(-500, 500), R.NextDouble(-500, 500), R.NextDouble(-500, 500));
                LensBatching.DrawLens(t, new Vector2(5, 5), new Vector2(0, 0), new Vector2(1, 1), new Vector4(1, 0, 0, 1), TC1);
                t = new DVector3(R.NextDouble(-500, 500), R.NextDouble(-500, 500), R.NextDouble(-500, 500));
                LensBatching.DrawLens(t, new Vector2(5, 5), new Vector2(0, 0), new Vector2(1, 1), new Vector4(0, 0, 1, 1), TC2);

            }

            LensBatching.DrawLens(Camera.position + Conversion.ToDoubleVector(-LightDirection)*Camera.farClip*0.95f, new Vector2(1000, 1000), new Vector2(0, 0), new Vector2(1, 1), new Vector4(1, 0, 0, 1), TC1);

            KDtree KDT = new KDtree(20, AllObjects);
            List<GameObject> InCam = KDT.GetBodyInCamera(Camera.position, Camera.LcameraFrustum);

            DebugDraw.ResetLine();
            
            //Depth
            device.ImmediateContext.OutputMerger.SetTargets(DepthMapStencilView, DepthTarget);
            device.ImmediateContext.ClearDepthStencilView(DepthMapStencilView, DepthStencilClearFlags.Depth, 1.0f, 0);
            context.ClearRenderTargetView(DepthTarget, new Color4(1.0f, 0.0f, 0.0f, 1.0f));
            for (int i = 0; i < InCam.Count; i++) if (InCam[i] != null) InCam[i].DrawDepth(device, Camera);

            PSSMsHelper.RenderAllSplits(device, Camera, Conversion.ToDoubleVector(LightDirection), KDT, InCam);
            //
            DrawHelper.OcclusionTest(device, Camera, DepthSRV);
            //
            context.Rasterizer.SetViewports(Program.viewport);
            context.OutputMerger.SetTargets(DepthStencilView, HDRTarget);
            context.ClearRenderTargetView(HDRTarget, new Color4(0.5f, 0.5f, 1.0f, 1.0f));
            context.ClearDepthStencilView(DepthStencilView, DepthStencilClearFlags.Depth, 1.0f, 0);

            //DebugDraw.AddLine(new DebugLine(KDT.Root.AA, KDT.Root.BB, new Vector3(1, 0, 0)));
            //DebugDraw.DrawAABB(KDT.Root.AA, KDT.Root.BB, new Vector3(1, 0, 0));
            //DebugDraw.DrawKDTree(KDT, new Vector3(1, 0, 0));
            //DebugDraw.DrawLocator(new DVector3(50, 50, 0), 5, new Vector3(0, 1, 0));
            SkyBox.Draw(TimeMili, device, context, Camera);

            /*
            for (int i = 0; i < InCam.Count; i++)
                if (InCam[i] != null)
                    InCam[i].Draw(device, Camera, Light, LightDirection);
            */
            
            PSSMsHelper.RenderObj(TimeMili, device, Camera, LightDirection, Light.Color, InCam);

            for (int i = 0; i < InCam.Count; i++)
                if (InCam[i] != null)
                    InCam[i].DrawEffects(device, Camera);

            LensBatching.BatchingDrawLens(device, Camera, DepthSRV);

            DebugDraw.Draw(device, Camera);

            /*
            foreach (GSSprite TS_ in TS)
            {
                TS_.Render(device, Camera, DepthSRV);
            }
            */

            //context.OutputMerger.SetTargets(renderTarget);
            //HDRSRV
            //DepthSRV
            QuadScreen.FinalRender(device, TimeMili, context, HDRSRV, DepthSRV, renderTarget);
            /*
            Int64 TStart = Program.HiTimer.Value;
            Int64 TStop = Program.HiTimer.Value;
            float Time = Program.HiTimer.Tick2Mili(TStop - TStart);
            Program.form.Text = Time.ToString();*/
        }

        public void Resize(Device device, SwapChain swapChain, DeviceContext context)
        {
            renderTarget.Dispose();
            //DepthStencilView.Dispose();

            swapChain.ResizeBuffers(2, 0, 0, Format.R8G8B8A8_UNorm, SwapChainFlags.AllowModeSwitch);
            using (var resource = Resource.FromSwapChain<Texture2D>(swapChain, 0))
                renderTarget = new RenderTargetView(device, resource);

            context.OutputMerger.SetTargets(renderTarget);
        }

        #region IDisposable
        ~Scene()
        {
            Dispose(false);
        }
        public void Dispose()
        {
            Dispose(true);
        }
        private void Dispose(bool disposeManagedObjects)
        {
            if (disposeManagedObjects)
            {
                SkyBox.Dispose();
                Light.Dispose();
                
                DepthStencilView.Dispose();
                DepthStencil.Dispose();
                HDRTarget.Dispose();
                HDRSRV.Dispose();
                HDRTexture.Dispose();
                HDRSS.Dispose();
                renderTarget.Dispose();

                DepthTexture.Dispose();
                DepthSRV.Dispose();
                DepthTarget.Dispose();
            }
        }
        #endregion
    }
}
